"""
socket分为阻塞和非阻塞两种，可以通过setsockopt，或者更简单的setblocking, settimeout设置。
阻塞式的socket的recv服从这样的规则：当缓冲区内有数据时，立即返回所有的数据；当缓冲区内无数据时，阻塞直到缓冲区中有数据。
非阻塞式的socket的recv服从的规则则是：当缓冲区内有数据时，立即返回所有的数据；当缓冲区内无数据时，产生EAGAIN的错误并返回（在Python中会抛出一个异常）。
两种情况都不会返回空字符串，返回空数据的结果是对方关闭了连接之后才会出现的。S

s.send() #发送TCP数据，将string中的数据发送到连接的套接字。返回值是要发送的字节数量，该数量可能小于string的字节大小。
s.sendall() #完整发送TCP数据，完整发送TCP数据。将string中的数据发送到连接的套接字，但在返回之前会尝试发送所有数据。成功返回None，失败则抛出异常。
s.recvfrom() #接收UDP数据，与recv()类似，但返回值是（data,address）。其中data是包含接收数据的字符串，address是发送数据的套接字地址。

"""


__description__ = \
"""
Base class for allowing connections between arduino and PyCmdMessenger instances
via USB.
"""
__author__ = "Michael J. Harms"
__date__ = "2016-05-30"

import socket, time
import select

class socket_connect:
    """
    Class for connecting to an Arduino board over USB using PyCmdMessenger.  
    The board holds the serial handle (which, in turn, holds the device name,
    baud rate, and timeout) and the board parameters (size of data types in 
    bytes, etc.).  The default parameters are for an ArduinoUno board.
    """

    def __init__(self,
                 client_socket,
                 timeout=5.0,
                 settle_time=2.0,
                 enable_dtr=False,
                 int_bytes=2,
                 long_bytes=4,
                 float_bytes=4,
                 double_bytes=4):

        """
        Serial connection parameters:
            
            device: serial device (e.g. /dev/ttyACM0)
            baud_rate: baud rate set in the compiled sketch
            timeout: timeout for serial reading and writing
            settle_time: how long to wait before trying to access serial port
            enable_dtr: use DTR (set to False to prevent arduino reset on connect)

        Board input parameters:
            int_bytes: number of bytes to store an integer
            long_bytes: number of bytes to store a long
            float_bytes: number of bytes to store a float
            double_bytes: number of bytes to store a double

        These can be looked up here:
            https://www.arduino.cc/en/Reference/HomePage (under data types)

        The default parameters work for ATMega328p boards.
        Note that binary strings are passed as little-endian (which should
        work for all arduinos)
        """

        self.connect = client_socket
        #self.baud_rate = baud_rate
        self.timeout = timeout
        self.settle_time = settle_time
        self.enable_dtr = enable_dtr

        self.int_bytes = int_bytes
        self.long_bytes = long_bytes
        self.float_bytes = float_bytes
        self.double_bytes = double_bytes
        #self.baud_rate = baud_rate

        # Open up the serial port
        self._is_connected = False
        self.open()


        #----------------------------------------------------------------------
        # Figure out proper type limits given the board specifications
        #----------------------------------------------------------------------

        self.int_min = -2**(8*self.int_bytes-1)
        self.int_max = 2**(8*self.int_bytes-1) - 1

        self.unsigned_int_min = 0
        self.unsigned_int_max = 2**(8*self.int_bytes) - 1

        self.long_min = -2**(8*self.long_bytes-1)
        self.long_max = 2**(8*self.long_bytes-1) - 1

        self.unsigned_long_min = 0
        self.unsigned_long_max = 2**(8*self.long_bytes)-1

        # Set to either IEEE 754 binary32 bit or binary64 bit
        if self.float_bytes == 4: 
            self.float_min = -3.4028235E+38
            self.float_max =  3.4028235E+38
        elif self.float_bytes == 8:
            self.float_min = -1e308
            self.float_max =  1e308
        else:
            err = "float bytes should be 4 (32 bit) or 8 (64 bit)"
            raise ValueError(err)
        
        if self.double_bytes == 4: 
            self.double_min = -3.4028235E+38
            self.double_max =  3.4028235E+38
        elif self.double_bytes == 8:
            self.double_min = -1e308
            self.double_max =  1e308
        else:
            err = "double bytes should be 4 (32 bit) or 8 (64 bit)"
            raise ValueError(err)

        #----------------------------------------------------------------------
        # Create a self.XXX_type for each type based on its byte number. This
        # type can then be passed into struct.pack and struct.unpack calls to
        # properly format the bytes strings.
        #----------------------------------------------------------------------

        INTEGER_TYPE = {2:"<h",4:"<i",8:"<l"}
        UNSIGNED_INTEGER_TYPE = {2:"<H",4:"<I",8:"<L"}
        FLOAT_TYPE = {4:"<f",8:"<d"}

        try:
            self.int_type = INTEGER_TYPE[self.int_bytes]
            self.unsigned_int_type = UNSIGNED_INTEGER_TYPE[self.int_bytes]
        except KeyError:
            keys = list(INTEGER_TYPE.keys())
            keys.sort()
            
            err = "integer bytes must be one of {}".format(keys())
            raise ValueError(err)

        try:
            self.long_type = INTEGER_TYPE[self.long_bytes]
            self.unsigned_long_type = UNSIGNED_INTEGER_TYPE[self.long_bytes]
        except KeyError:
            keys = list(INTEGER_TYPE.keys())
            keys.sort()
            
            err = "long bytes must be one of {}".format(keys())
            raise ValueError(err)
    
        try:
            self.float_type = FLOAT_TYPE[self.float_bytes]
            self.double_type = FLOAT_TYPE[self.double_bytes]
        except KeyError:
            keys = list(self.FLOAT_TYPE.keys())
            keys.sort()
            
            err = "float and double bytes must be one of {}".format(keys())
            raise ValueError(err)

    def open(self):
        """
        Open the serial connection.
        """
        """
        if not self._is_connected:
            
            print("Connecting to arduino on {}... ".format(self.device),end="")

            self.comm = serial.Serial()
            self.comm.port = self.device
            self.comm.baudrate = self.baud_rate
            self.comm.timeout = self.timeout
            self.dtr = self.enable_dtr
            self.comm.open()

            time.sleep(self.settle_time)
            self._is_connected = True

            print("done.")
        """
    def read(self):
        """
        Wrap serial read method.
        """
        self.connect.setblocking(False)
        timeout_in_seconds = 5
        ready = select.select([self.connect], [], [], timeout_in_seconds) #会阻塞到timeout或有数据
        if ready[0]:
            data = self.connect.recv(1) #最大1024， 不够也会返回
            print("py-----socket")
            print(data)

        else:
            print("tcp recv timeout")
            data = b''
        #self.connect.settimeout(10)
        #data = self.connect.recv(1)
        
        self.connect.setblocking(True)
        #print(data)
        return data

    def read_num(self,num):  #never call
        """
        Wrap serial read method.
        """

        return self.connect.recv(num)        
        
    def readline(self): #never call
        """
        Wrap serial readline method.
        """
        
        return "" #self.connect.readline()

    def write(self,msg):
        """
        Wrap serial write method.
        """
        self.connect.sendall(msg)
        
    def flush(self):
        """
        Wrap serial write method.
        """
        
        return #self.connect.flush()        

    def close(self):
        """
        Close serial connection.
        """

        if self._is_connected:
            self.connect.close()
        self._is_connected = False

    def clearSocketBuffer(self, socket):
        while True:
            timeout_in_seconds = 0.5
            ready = select.select([socket], [], [], timeout_in_seconds)
            if ready[0]:
                data = socket.recv(1024) #最大1024， 不够也会返回
                #print("clear in buff:", data)
            else:
                #print("socket buff is clear ")
                return

        
    def reset_input_buffer  (self,socket):
        #return
        
        #self.connect.reset_input_buffer()
        #print("call reset_input_buffer")
        socket.setblocking(False) # 需要先设置为非阻塞, 使用select超时机制清空
        #timeout_in_seconds = 0.5
        self.clearSocketBuffer(socket)
        socket.setblocking(True) # 设置阻塞, 等待数据读取
      
        
    @property
    def connected(self):
        """
        Return connection state.  Connected (True), disconnected (False).
        """
        try:
                # 获取socket的连接状态
            self.connect.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
            self._is_connected = True
            return True
        except socket.error as e:
            # 如果发生异常，表示socket连接未建立
            self._is_connected = False
            return False
